/*
 * PROJECT:     ReactOS Boot Sector for ISO file system (based on ISOLINUX)
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     Booting ReactOS off a CD-ROM using the El Torito boot standard in "no emulation mode"
 * COPYRIGHT:   Copyright 1994-2009 H. Peter Anvin
 *              Copyright 2002 Michael K. Ter Louw
 *              Copyright 2002 Eric Kohl
 *              Copyright 2009 Intel Corporation *author: H. Peter Anvin
 *              Copyright 2011 Timo Kreuzer (timo.kreuzer@reactos.org)
 *              Copyright 2017 Colin Finck (colin@reactos.org)
 */

/* INCLUDES ******************************************************************/
#include <asm.inc>
#include <freeldr/include/arch/pc/x86common.h>

#ifndef ROS_REGTEST
#define WAIT_FOR_KEY
#endif


.code16
ASSUME CS:.text, DS:.text, ES:.text

/* CONSTANTS ******************************************************************/
BIOS_timer = HEX(046C)      // Timer ticks (1 word)
BIOS_magic = HEX(0472)      // BIOS reset magic (1 word)

// Memory below this point is reserved for the BIOS and the MBR
trackbuf = HEX(1000)        // Track buffer goes here (8192 bytes)
trackbufsize = 8192         // trackbuf ends at 3000h

// struct open_file_t
file_sector = 0             // Sector pointer (0 = structure free)
file_bytesleft = 4          // Number of bytes left
file_left = 8               // Number of sectors left
// Another unused DWORD follows here in ISOLINUX
#define open_file_t_size 16

// struct dir_t
dir_lba = 0                 // Directory start (LBA)
dir_len = 4                 // Length in bytes
dir_clust = 8               // Length in clusters
#define dir_t_size 12

MAX_OPEN_LG2 = 2            // log2(Max number of open files)
MAX_OPEN = 4
SECTOR_SHIFT = 11           // 2048 bytes/sector (El Torito requirement)
SECTOR_SIZE = 2048
retry_count = 6             // How patient are we with the BIOS?

/* UNINITIALIZED VARIABLES ****************************************************/
absolute HEX(5000)          // Here we keep our BSS stuff

resb ISOFileName, 64        // ISO filename canonicalization buffer
resb ISOFileNameEnd, 1
resb CurrentDir, dir_t_size // Current directory
resb RootDir, dir_t_size    // Root directory
resb DiskSys, 2             // Last INT 13h call
resb GetlinsecPtr, 2        // The sector-read pointer
resb DiskError, 1           // Error code for disk I/O
resb DriveNumber, 1         // CD-ROM BIOS drive number
resb ISOFlags, 1            // Flags for ISO directory search
resb RetryCount, 1          // Used for disk access retries

//align open_file_t_size
absolute HEX(5070)
resb Files, (MAX_OPEN * open_file_t_size)


/* ENTRY POINTS ***************************************************************/

// Entry point when booted from CD (El Torito standard)
start:
    mov bx, offset getlinsec_cdrom
    // Fall through

start_common:
    // Set up our stack and a flat addressing model.
    cli
    xor ax, ax
    mov ss, ax
    mov sp, offset start
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    sti

    // Our boot sector has been loaded to address 0x7C00.
    // Relocate our 2048 bytes boot sector to the given base address (should be 0x7000).
    cld
    mov cx, 2048 / 4
    mov si, HEX(7C00)
    mov di, offset start
    rep movsd

    ljmp16 0, relocated     // jump into relocated code

.org 64
hybrid_signature:
    .long HEX(7078c0fb)

// Entry point when booted through ISOMBR from a drive (isohybrid mode)
start_hybrid:
    mov bx, offset getlinsec_ebios
    jmp start_common

relocated:
    // Save our passed variables (BX from the entry point, DL from the BIOS) before anybody clobbers the registers.
    mov word ptr ds:[GetlinsecPtr], bx
    mov byte ptr ds:[DriveNumber], dl

    // Make sure the keyboard buffer is empty
    call pollchar_and_empty

    // If we're booting in hybrid mode and our boot drive is the first HDD (drive 80h),
    // we have no other option than booting into SETUPLDR.
    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
    jne .read_mbr
    cmp byte ptr ds:[DriveNumber], HEX(80)
    je .boot_setupldr

.read_mbr:
    // Read the first sector (MBR) from the first hard disk (drive 80h) to 7C00h.
    // If we then decide to boot from HDD, we already have it at the right place.
    // In case of an error (indicated by the Carry Flag), just boot SETUPLDR from our ReactOS medium.
    mov ax, HEX(0201)
    mov dx, HEX(0080)
    mov cx, HEX(0001)
    mov bx, HEX(7C00)
    call int13
    jc .boot_setupldr

    // Verify the signature of the read MBR.
    // If it's invalid, there is probably no OS installed and we just boot SETUPLDR from our ReactOS medium.
    mov ax, word ptr ds:[HEX(7C00)+510]
    cmp ax, HEX(AA55)
    jne .boot_setupldr

#ifdef WAIT_FOR_KEY
    // We could either boot from the ReactOS medium or from hard disk. Let the user decide!
    // Display the 'Press key' message.
    call crlf_early
    mov si, offset presskey_msg
    call writestr_early

    // Count down 5 seconds.
    mov cx, 5

.next_second:
    // Count in seconds using the BIOS Timer, which runs roughly at 19 ticks per second.
    // Load its value plus one second into EAX for comparison later.
    mov eax, ds:[BIOS_timer]
    add eax, 19

.poll_again:
    // Check for a keypress, boot SETUPLDR from our ReactOS medium if a key was pressed.
    call pollchar_and_empty
    jnz .boot_setupldr

    // Check if another second has passed (in BIOS Timer ticks).
    mov ebx, ds:[BIOS_timer]
    cmp eax, ebx
    jnz .poll_again

    // Another second has passed, so print the dot and decrement the second counter.
    // If the user hasn't pressed a key after the entire 5 seconds have elapsed, just boot from the first hard disk.
    mov si, offset dot_msg
    call writestr_early
    dec cx
    jz .boot_harddisk
    jmp .next_second
#endif

.boot_harddisk:
    // Restore a clean context for the hard disk MBR and boot the already loaded MBR.
    call crlf_early
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    mov dx, HEX(0080)

    ljmp16 0, HEX(7C00)

.boot_setupldr:
#ifdef WAIT_FOR_KEY
    call crlf_early
    call crlf_early
#endif

    // The BIOS gave us a boot drive number, so in a perfect world we could just use that one now.
    // Unfortunately, there are many broken BIOSes around, which is why ISOLINUX verifies it and applies some hacks if the number is wrong.
    // Let's do exactly the same here to achieve maximum compatibility.

    // Don't do this if we are running in hybrid mode.
    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
    je found_drive

    // Use the INT 13 function 4B01h (Get Disk Emulation Status) to fetch the El Torito Spec Packet.
    // We can use this information to verify that our passed boot drive number really belongs to our CD.
    mov ax, HEX(4B01)
    mov dl, byte ptr ds:[DriveNumber]
    mov si, offset spec_packet
    call int13

    // If this INT 13 function yields an error, we may be on a broken AWARD BIOS.
    // Check this and patch if possible.
    jc award_hack

    // Check that our passed boot drive number and the number in the Spec Packet match.
    // If not, try some workarounds to find our drive anyway.
    mov dl, byte ptr ds:[DriveNumber]
    cmp byte ptr ds:[sp_drive], dl
    jne spec_query_failed

found_drive:
    // Clear Files structures
    mov di, Files
    mov cx, (MAX_OPEN*open_file_t_size)/4
    xor eax, eax
    rep stosd

    // Read the entire 2K-sized ISO9660 Primary Volume Descriptor at sector 16 (32K).
    // This calculation only holds for single-session ISOs, but we should never encounter anything else.
    mov eax, 16
    mov bx, trackbuf
    call getonesec

    // Read the LBA address (offset 2 in the Directory Record) of the root directory (offset 156 in the Primary Volume Descriptor).
    mov eax, dword ptr ds:[trackbuf+156+2]
    mov dword ptr ds:[RootDir+dir_lba], eax
    mov dword ptr ds:[CurrentDir+dir_lba], eax

    // Read the data length (offset 10 in the Directory Record) of the root directory (offset 156 in the Primary Volume Descriptor).
    mov eax, dword ptr ds:[trackbuf+156+10]
    mov dword ptr ds:[RootDir+dir_len], eax
    mov dword ptr ds:[CurrentDir+dir_len], eax

    // Calculate the number of clusters and write that to our RootDir and CurrentDir structures.
    add eax, SECTOR_SIZE-1
    shr eax, SECTOR_SHIFT
    mov dword ptr ds:[RootDir+dir_clust],eax
    mov dword ptr ds:[CurrentDir+dir_clust],eax

    // Look for the "LOADER" directory (directory is indicated by AL = 2 when using searchdir_iso).
    mov di, offset loader_dir
    mov al, 2
    call searchdir_iso
    jnz .dir_found

    // No directory was found, so bail out with an error message.
    mov si, offset no_dir_msg
    call writemsg
    jmp kaboom

.dir_found:
    // The directory was found, so update the information in our CurrentDir structure.
    // Free the file pointer entry at SI in the process.
    mov dword ptr ds:[CurrentDir+dir_len], eax
    mov eax, dword ptr ds:[si+file_left]
    mov dword ptr ds:[CurrentDir+dir_clust], eax
    xor eax, eax
    xchg eax, dword ptr ds:[si+file_sector]
    mov dword ptr ds:[CurrentDir+dir_lba], eax

    // Look for the "SETUPLDR.SYS" file.
    mov di, offset setupldr_sys
    call searchdir
    jnz .setupldr_found

    // The SETUPLDR file was not found, so bail out with an error message.
    mov si, offset no_setupldr_msg
    call writemsg
    jmp kaboom

.setupldr_found:
    // Calculate the rounded up number of 2K sectors that need to be read.
    mov ecx, eax
    shr ecx, SECTOR_SHIFT
    test eax, HEX(7FF)
    jz .load_setupldr
    inc ecx

.load_setupldr:
    // Load the entire SETUPLDR.SYS (parameter CX = FFFFh) to its designated base address FREELDR_BASE.
    // Using a high segment address with offset 0 instead of segment 0 with offset FREELDR_BASE apparently increases compatibility with some BIOSes.
    mov bx, FREELDR_BASE / 16
    mov es, bx
    xor ebx, ebx
    mov cx, HEX(FFFF)
    call getfssec

    // Pass two parameters to SETUPLDR:
    //    DL = BIOS Drive Number
    //    DH = Boot Partition (0 for HDD booting in hybrid mode, FFh for CD booting)
    movzx dx, byte ptr ds:[DriveNumber]
    cmp word ptr ds:[GetlinsecPtr], offset getlinsec_ebios
    je .jump_to_setupldr
    mov dh, HEX(FF)

.jump_to_setupldr:
    // Transfer execution to the bootloader.
    ljmp16 0, FREELDR_BASE


/* FUNCTIONS *****************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Start of BrokenAwardHack --- 10-nov-2002           Knut_Petersen@t-online.de
///////////////////////////////////////////////////////////////////////////////
//
// There is a problem with certain versions of the AWARD BIOS ...
// the boot sector will be loaded and executed correctly, but, because the
// int 13 vector points to the wrong code in the BIOS, every attempt to
// load the spec packet will fail. We scan for the equivalent of
//
//     mov ax,0201h
//     mov bx,7c00h
//     mov cx,0006h
//     mov dx,0180h
//     pushf
//     call <direct far>
//
// and use <direct far> as the new vector for int 13. The code above is
// used to load the boot code into ram, and there should be no reason
// for anybody to change it now or in the future. There are no opcodes
// that use encodings relativ to IP, so scanning is easy. If we find the
// code above in the BIOS code we can be pretty sure to run on a machine
// with an broken AWARD BIOS ...
//
///////////////////////////////////////////////////////////////////////////////
award_oldint13:
    .long 0
award_string:
    .byte HEX(0b8),1,2,HEX(0bb),0,HEX(7c),HEX(0b9),6,0,HEX(0ba),HEX(80),1,HEX(09c),HEX(09a)

award_hack:
    mov si, offset spec_err_msg                         // Moved to this place from
    call writemsg                                       // spec_query_failed

    mov eax, dword ptr ds:[HEX(13)*4]
    mov dword ptr ds:[award_oldint13], eax

    push es
    mov ax, HEX(F000)                                   // ES = BIOS Seg
    mov es, ax
    cld
    xor di, di                                          // start at ES:DI = f000:0
award_loop:
    push di                                             // save DI
    mov si, offset award_string                         // scan for award_string
    mov cx, 7                                           // length of award_string = 7dw
    repz cmpsw                                          // compare
    pop di                                              // restore DI
    jcxz award_found                                    // jmp if found
    inc di                                              // not found, inc di
    jno award_loop

award_failed:
    pop es                                              // No, not this way :-((
award_fail2:
    mov eax, dword ptr ds:[award_oldint13]              // restore the original int
    or eax, eax                                         // 13 vector if there is one
    jz spec_query_failed                                // and try other workarounds
    mov dword ptr ds:[HEX(13)*4], eax
    jmp spec_query_failed

award_found:
    mov eax, dword ptr es:[di+HEX(0e)]                  // load possible int 13 addr
    pop es                                              // restore ES

    cmp eax, dword ptr ds:[award_oldint13]              // give up if this is the
    jz award_failed                                     // active int 13 vector,
    mov dword ptr ds:[HEX(13)*4], eax                   // otherwise change 0:13h*4

    mov ax, HEX(4B01)                                   // try to read the spec packet
    mov dl, byte ptr ds:[DriveNumber]                   // now ... it should not fail
    mov si, offset spec_packet                          // any longer
    int HEX(13)
    jc award_fail2

    jmp found_drive                                     // and leave error recovery code
///////////////////////////////////////////////////////////////////////////////
// End of BrokenAwardHack ----            10-nov-2002 Knut_Petersen@t-online.de
///////////////////////////////////////////////////////////////////////////////


// INT 13h, AX=4B01h, DL=<passed in value> failed.
// Try to scan the entire 80h-FFh from the end.
spec_query_failed:
    // some code moved to BrokenAwardHack

    mov dl, HEX(FF)

.test_loop:
    pusha
    mov ax, HEX(4B01)
    mov si, offset spec_packet
    mov byte ptr ds:[si], HEX(13)                       // Size of buffer
    call int13
    popa
    jc .still_broken

    mov si, offset maybe_msg
    call writemsg
    mov al, dl
    call writehex2
    call crlf_early

    cmp byte ptr ds:[sp_drive], dl
    jne .maybe_broken

    // Okay, good enough...
    mov si, offset alright_msg
    call writemsg
.found_drive0:
    mov byte ptr ds:[DriveNumber], dl
.found_drive:
    jmp found_drive

    // Award BIOS 4.51 apparently passes garbage in sp_drive,
    // but if this was the drive number originally passed in
    // DL then consider it "good enough"
.maybe_broken:
    mov al, byte ptr ds:[DriveNumber]
    cmp al, dl
    je .found_drive

    // Intel Classic R+ computer with Adaptec 1542CP BIOS 1.02
    // passes garbage in sp_drive, and the drive number originally
    // passed in DL does not have 80h bit set.
    or al, HEX(80)
    cmp al, dl
    je .found_drive0

.still_broken:
    dec dx
    cmp dl, HEX(80)
    jnb .test_loop

    // No spec packet anywhere.  Some particularly pathetic
    // BIOSes apparently don't even implement function
    // 4B01h, so we can't query a spec packet no matter
    // what.  If we got a drive number in DL, then try to
    // use it, and if it works, then well...
    mov dl, byte ptr ds:[DriveNumber]
    cmp dl, HEX(81)                                     // Should be 81-FF at least
    jb fatal_error                                      // If not, it's hopeless

    // Write a warning to indicate we're on *very* thin ice now
    mov si, offset nospec_msg
    call writemsg
    mov al, dl
    call writehex2
    call crlf_early
    jmp .found_drive                                    // Pray that this works...

fatal_error:
    mov si, offset nothing_msg
    call writemsg

.norge:
    jmp short .norge

//
// searchdir:
//
//      Open a file
//
//          On entry:
//              DS:DI   = filename
//          If successful:
//              ZF clear
//              SI      = file pointer
//              EAX     = file length in bytes
//          If unsuccessful
//              ZF set
//
// Assumes CS == DS == ES, and trashes BX and CX.
//
// searchdir_iso is a special entry point for ISOLINUX only.  In addition
// to the above, searchdir_iso passes a file flag mask in AL.  This is useful
// for searching for directories.
//
alloc_failure:
    xor ax, ax                                  // ZF <- 1
    ret

searchdir:
    xor al, al
searchdir_iso:
    mov byte ptr ds:[ISOFlags], al
    call allocate_file                          // Temporary file structure for directory
    jnz alloc_failure
    push es
    push ds
    pop es                                      // ES = DS
    mov si, offset CurrentDir
    cmp byte ptr ds:[di], '/'                   // If filename begins with slash
    jne .not_rooted
    inc di                                      // Skip leading slash
    mov si, offset RootDir                      // Reference root directory instead
.not_rooted:
    mov eax, dword ptr ds:[si+dir_clust]
    mov dword ptr ds:[bx+file_left], eax
    shl eax, SECTOR_SHIFT
    mov dword ptr ds:[bx+file_bytesleft], eax
    mov eax, dword ptr ds:[si+dir_lba]
    mov dword ptr ds:[bx+file_sector], eax
    mov edx, dword ptr ds:[si+dir_len]

.look_for_slash:
    mov ax, di
.scan:
    mov cl, byte ptr ds:[di]
    inc di
    and cl, cl
    jz .isfile
    cmp cl, '/'
    jne .scan
    mov byte ptr ds:[di-1], 0                   // Terminate at directory name
    mov cl, 2                                   // Search for directory
    xchg cl, byte ptr ds:[ISOFlags]

    push di                                     // Save these...
    push cx

    // Create recursion stack frame...
    push offset .resume                         // Where to "return" to
    push es
.isfile:
    xchg ax, di

.getsome:
    // Get a chunk of the directory
    // This relies on the fact that ISOLINUX doesn't change SI
    mov si, trackbuf
    pushad
    xchg bx, si
    mov cx, word ptr ds:[BufSafe]
    call getfssec
    popad

.compare:
    movzx eax, byte ptr ds:[si]                 // Length of directory entry
    cmp al, 33
    jb .next_sector
    mov cl, byte ptr ds:[si+25]
    xor cl, byte ptr ds:[ISOFlags]
    test cl, HEX(8E)                            // Unwanted file attributes!
    jnz .not_file
    pusha
    movzx cx, byte ptr ds:[si+32]               // File identifier length
    add si, 33                                  // File identifier offset
    call iso_compare_names
    popa
    je .success
.not_file:
    sub edx, eax                                // Decrease bytes left
    jbe .failure
    add si, ax                                  // Advance pointer

.check_overrun:
    // Did we finish the buffer?
    cmp si, trackbuf+trackbufsize
    jb .compare                                 // No, keep going

    jmp short .getsome                          // Get some more directory

.next_sector:
    // Advance to the beginning of next sector
    lea ax, [si+SECTOR_SIZE-1]
    and ax, not (SECTOR_SIZE-1)
    sub ax, si
    jmp short .not_file                         // We still need to do length checks

.failure:
    xor eax, eax                                // ZF = 1
    mov dword ptr ds:[bx+file_sector], eax
    pop es
    ret

.success:
    mov eax, dword ptr ds:[si+2]                // Location of extent
    mov dword ptr ds:[bx+file_sector], eax
    mov eax, dword ptr ds:[si+10]               // Data length
    mov dword ptr ds:[bx+file_bytesleft], eax
    push eax
    add eax, SECTOR_SIZE-1
    shr eax, SECTOR_SHIFT
    mov dword ptr ds:[bx+file_left], eax
    pop eax
    jz .failure                                 // Empty file?
    // ZF = 0
    mov si, bx
    pop es
    ret

.resume:
    // We get here if we were only doing part of a lookup
    // This relies on the fact that .success returns bx == si
    xchg edx, eax                               // Directory length in edx
    pop cx                                      // Old ISOFlags
    pop di                                      // Next filename pointer
    mov byte ptr ds:[di-1], '/'                 // Restore slash
    mov byte ptr ds:[ISOFlags], cl              // Restore the flags
    jz .failure                                 // Did we fail?  If so fail for real!
    jmp .look_for_slash                         // Otherwise, next level

//
// allocate_file: Allocate a file structure
//
//        If successful:
//          ZF set
//          BX = file pointer
//        In unsuccessful:
//          ZF clear
//
allocate_file:
    push cx
    mov bx, Files
    mov cx, MAX_OPEN
.check:
    cmp dword ptr ds:[bx], 0
    je .found
    add bx, open_file_t_size                    // ZF = 0
    loop .check
    // ZF = 0 if we fell out of the loop
.found:
    pop cx
    ret

//
// iso_compare_names:
//    Compare the names DS:SI and DS:DI and report if they are
//    equal from an ISO 9660 perspective.  SI is the name from
//    the filesystem; CX indicates its length, and ';' terminates.
//    DI is expected to end with a null.
//
//    Note: clobbers AX, CX, SI, DI; assumes DS == ES == base segment
//
iso_compare_names:
    // First, terminate and canonicalize input filename
    push di
    mov di, offset ISOFileName
.canon_loop:
    jcxz .canon_end
    lodsb
    dec cx
    cmp al, ';'
    je .canon_end
    and al, al
    je .canon_end
    stosb
    cmp di, offset ISOFileNameEnd-1             // Guard against buffer overrun
    jb .canon_loop
.canon_end:
    cmp di, ISOFileName
    jbe .canon_done
    cmp byte ptr ds:[di-1], '.'                 // Remove terminal dots
    jne .canon_done
    dec di
    jmp short .canon_end
.canon_done:
    mov byte ptr ds:[di], 0                     // Null-terminate string
    pop di
    mov si, ISOFileName
.compare2:
    lodsb
    mov ah, byte ptr ds:[di]
    inc di
    and ax, ax
    jz .success2                                // End of string for both
    and al, al                                  // Is either one end of string?
    jz .failure2                                // If so, failure
    and ah, ah
    jz .failure2
    or ax, HEX(2020)                            // Convert to lower case
    cmp al, ah
    je .compare2
.failure2:
    and ax, ax                                  // ZF = 0 (at least one will be nonzero)
.success2:
    ret

//
// getfssec: Get multiple clusters from a file, given the file pointer.
//
//  On entry:
//       ES:BX   -> Buffer
//       SI      -> File pointer
//       CX      -> Cluster count
//  On exit:
//       SI      -> File pointer (or 0 on EOF)
//       CF = 1  -> Hit EOF
//       ECX     -> Bytes actually read
//
getfssec:
    push ds
    push cs
    pop ds                                      // DS <- CS

    movzx ecx, cx
    cmp ecx, dword ptr ds:[si+file_left]
    jna .ok_size
    mov ecx, dword ptr ds:[si+file_left]
.ok_size:
    pushad
    mov eax, dword ptr ds:[si+file_sector]
    mov bp, cx
    call getlinsec
    popad

    // ECX[31:16] == 0 here...
    add dword ptr ds:[si+file_sector], ecx
    sub dword ptr ds:[si+file_left], ecx
    shl ecx, SECTOR_SHIFT                       // Convert to bytes
    cmp ecx, dword ptr ds:[si+file_bytesleft]
    jb .not_all
    mov ecx, dword ptr ds:[si+file_bytesleft]
.not_all:
    sub dword ptr ds:[si+file_bytesleft], ecx
    jnz .ret                                    // CF = 0 in this case...
    push eax
    xor eax, eax
    mov dword ptr ds:[si+file_sector], eax      // Unused
    mov si, ax
    pop eax
    stc
.ret:
    pop ds
    ret

//
// Information message (DS:SI) output
// Prefix with "ISOBOOT: "
//
writemsg:
    push ax
    push si
    mov si, offset isoboot_str
    call writestr_early
    pop si
    call writestr_early
    pop ax
    ret

writestr_early:
    pushfd
    pushad
.top:
    lodsb
    and al, al
    jz .end_writestr
    call writechr
    jmp short .top
.end_writestr:
    popad
    popfd
    ret

crlf_early:
    push ax
    mov al, 13
    call writechr
    mov al, 10
    call writechr
    pop ax
    ret

//
// writechr: Write a character to the screen.
//
writechr:
    pushfd
    pushad
    mov ah, HEX(0E)
    xor bx, bx
    int HEX(10)
    popad
    popfd
    ret

//
// int13: save all the segment registers and call INT 13h.
// Some CD-ROM BIOSes have been found to corrupt segment registers
// and/or disable interrupts.
//
int13:
    pushf
    push bp
    push ds
    push es
    push fs
    push gs
    int HEX(13)
    mov bp, sp
    setc byte ptr ds:[bp+10]                    // Propagate CF to the caller
    pop gs
    pop fs
    pop es
    pop ds
    pop bp
    popf
    ret

//
// Get one sector.  Convenience entry point.
//
getonesec:
    mov bp, 1
    // Fall through to getlinsec

//
// Get linear sectors - EBIOS LBA addressing, 2048-byte sectors.
//
getlinsec:
    jmp word ptr cs:[GetlinsecPtr]

//
// getlinsec_ebios:
//
// getlinsec implementation for floppy/HDD EBIOS (EDD)
//
getlinsec_ebios:
    xor edx, edx
    shld edx, eax, 2
    shl eax, 2                                  // Convert to HDD sectors
    shl bp, 2

.loop_ebios:
    push bp                                     // Sectors left
.retry2:
    call maxtrans                               // Enforce maximum transfer size
    movzx edi, bp                               // Sectors we are about to read
    mov cx, retry_count
.retry:
    // Form DAPA on stack
    push edx
    push eax
    push es
    push bx
    push di
    push 16
    mov si, sp
    pushad
    mov dl, byte ptr ds:[DriveNumber]
    push ds
    push ss
    pop ds                                      // DS <- SS
    mov ah, HEX(42)                             // Extended Read
    call int13
    pop ds
    popad
    lea sp, [si+16]                             // Remove DAPA
    jc .error_ebios
    pop bp
    add eax, edi                                // Advance sector pointer
    adc edx, 0
    sub bp, di                                  // Sectors left
    shl di, 9                                   // 512-byte sectors
    add bx, di                                  // Advance buffer pointer
    jnc .no_overflow                            // Check if we have read more than 64K and need to adjust ES
    mov di, es
    add di, HEX(1000)                           // Adjust segment by 64K (1000h * 16 = 10000h = 64K + 1)
    mov es, di
.no_overflow:
    and bp, bp
    jnz .loop_ebios

    ret

.error_ebios:
    pushad                                      // Try resetting the device
    xor ax, ax
    mov dl, byte ptr ds:[DriveNumber]
    call int13
    popad
    loop .retry                                 // CX-- and jump if not zero

    // Total failure.
    jmp kaboom

//
// Truncate BP to MaxTransfer
//
maxtrans:
    cmp bp, word ptr ds:[MaxTransfer]
    jna .ok
    mov bp, word ptr ds:[MaxTransfer]
.ok:
    ret

//
// This is the variant we use for real CD-ROMs:
// LBA, 2K sectors, some special error handling.
//
getlinsec_cdrom:
    mov si, offset dapa                         // Load up the DAPA
    mov word ptr ds:[si+4], bx
    mov word ptr ds:[si+6], es
    mov dword ptr ds:[si+8], eax
.loop_cdrom:
    push bp                                     // Sectors left
    cmp bp, word ptr ds:[MaxTransferCD]
    jbe .bp_ok
    mov bp, word ptr ds:[MaxTransferCD]
.bp_ok:
    mov word ptr ds:[si+2], bp
    push si
    mov dl, byte ptr ds:[DriveNumber]
    mov ah, HEX(42)                             // Extended Read
    call xint13
    pop si
    pop bp
    movzx eax, word ptr ds:[si+2]               // Sectors we read
    add dword ptr ds:[si+8], eax                // Advance sector pointer
    sub bp, ax                                  // Sectors left
    shl ax, SECTOR_SHIFT-4                      // 2048-byte sectors -> segment
    add word ptr ds:[si+6], ax                  // Advance buffer pointer
    and bp, bp
    jnz .loop_cdrom
    mov eax, dword ptr ds:[si+8]                // Next sector
    ret

    // INT 13h with retry
xint13:
    mov byte ptr ds:[RetryCount], retry_count
.try:
    pushad
    call int13
    jc .error_cdrom
    add sp, 8*4                                 // Clean up stack
    ret
.error_cdrom:
    mov byte ptr ds:[DiskError], ah             // Save error code
    popad
    mov word ptr ds:[DiskSys], ax               // Save system call number
    dec byte ptr ds:[RetryCount]
    jz .real_error
    push ax
    mov al, byte ptr ds:[RetryCount]
    mov ah, byte ptr ds:[dapa+2]                // Sector transfer count
    cmp al, 2                                   // Only 2 attempts left
    ja .nodanger
    mov ah, 1                                   // Drop transfer size to 1
    jmp short .setsize
.nodanger:
    cmp al, retry_count-2
    ja .again                                   // First time, just try again
    shr ah, 1                                   // Otherwise, try to reduce
    adc ah, 0                                   // the max transfer size, but not to 0
.setsize:
    mov byte ptr ds:[MaxTransferCD], ah
    mov byte ptr ds:[dapa+2], ah
.again:
    pop ax
    jmp .try

.real_error:
    mov si, offset diskerr_msg
    call writemsg
    mov al, byte ptr ds:[DiskError]
    call writehex2
    mov si, offset oncall_str
    call writestr_early
    mov ax, word ptr ds:[DiskSys]
    call writehex4
    mov si, offset ondrive_str
    call writestr_early
    mov al, dl
    call writehex2
    call crlf_early
    // Fall through to kaboom

//
// kaboom: write a message and bail out.  Wait for a user keypress,
//      then do a hard reboot.
//
kaboom:
    // Restore a clean context.
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
    sti

    // Display the failure message.
    mov si, offset err_bootfailed
    call writestr_early

    // Wait for a keypress.
    xor ax, ax
    int HEX(16)

    // Disable interrupts and reset the system through a magic BIOS call.
    cli
    mov word ptr ds:[BIOS_magic], 0
    ljmp16 HEX(0F000), HEX(0FFF0)

//
// writehex[248]: Write a hex number in (AL, AX, EAX) to the console
//
writehex2:
    pushfd
    pushad
    rol eax, 24
    mov cx,2
    jmp short writehex_common
writehex4:
    pushfd
    pushad
    rol eax, 16
    mov cx, 4
    jmp short writehex_common
writehex8:
    pushfd
    pushad
    mov cx, 8
writehex_common:
.loop_writehex:
    rol eax, 4
    push eax
    and al, HEX(0F)
    cmp al, 10
    jae .high
.low:
    add al, '0'
    jmp short .ischar
.high:
    add al, 'A'-10
.ischar:
    call writechr
    pop eax
    loop .loop_writehex
    popad
    popfd
    ret

//
// pollchar_and_empty: Check if we have an input character pending (ZF = 0)
// and empty the input buffer afterwards.
//
pollchar_and_empty:
    pushad
    mov ah, 1                                   // Did the user press a key?
    int HEX(16)
    jz .end_pollchar                            // No, then we're done
    mov ah, 0                                   // Otherwise empty the buffer by reading it
    int HEX(16)
.end_pollchar:
    popad
    ret


/* INITIALIZED VARIABLES *****************************************************/
presskey_msg:
    .ascii "Press any key to boot from the ReactOS medium", NUL
dot_msg:
    .ascii ".", NUL
isoboot_str:
    .ascii "ISOBOOT: ", NUL
spec_err_msg:
    .ascii "Loading spec packet failed, trying to wing it...", CR, LF, NUL
maybe_msg:
    .ascii "Found something at drive = ", NUL
alright_msg:
    .ascii "Looks reasonable, continuing...", CR, LF, NUL
nospec_msg:
    .ascii "Extremely broken BIOS detected, last attempt with drive = ", NUL
nothing_msg:
    .ascii "Failed to locate CD-ROM device; boot failed.", CR, LF, NUL
diskerr_msg:
    .ascii "Disk error ", NUL
oncall_str:
    .ascii ", AX = ", NUL
ondrive_str:
    .ascii ", drive ", NUL
err_bootfailed:
    .ascii CR, LF, "Boot failed: press a key to retry...", NUL
loader_dir:
    .ascii "/LOADER", NUL
no_dir_msg:
    .ascii "LOADER dir not found.", CR, LF, NUL
setupldr_sys:
    .ascii "SETUPLDR.SYS", NUL
no_setupldr_msg:
    .ascii "SETUPLDR.SYS not found.", CR, LF, NUL

.align 4
BufSafe:
    .word trackbufsize/SECTOR_SIZE              // Clusters we can load into trackbuf

// Maximum transfer size
.align 4
MaxTransfer:
    .word 127                                   // Hard disk modes
MaxTransferCD:
    .word 32                                    // CD mode

//
// El Torito spec packet
//
.align 8
spec_packet:
    .byte HEX(13)                               // Size of packet
sp_media:
    .byte 0                                     // Media type
sp_drive:
    .byte 0                                     // Drive number
sp_controller:
    .byte 0                                     // Controller index
sp_lba:
    .long 0                                     // LBA for emulated disk image
sp_devspec:
    .word 0                                     // IDE/SCSI information
sp_buffer:
    .word 0                                     // User-provided buffer
sp_loadseg:
    .word 0                                     // Load segment
sp_sectors:
    .word 0                                     // Sector count
sp_chs:
    .byte 0,0,0                                 // Simulated CHS geometry
sp_dummy:
    .byte 0                                     // Scratch, safe to overwrite

//
// EBIOS disk address packet
//
.align 8
dapa:
    .word 16                                    // Packet size
.count:
    .word 0                                     // Block count
.off:
    .word 0                                     // Offset of buffer
.seg:
    .word 0                                     // Segment of buffer
.lba:
    .long 0                                     // LBA (LSW)
    .long 0                                     // LBA (MSW)


// Extend the size to cover one 2K-sized sector
.org 2047
    .byte 0

.endcode16

END
